/*!
    \copyright  Copyright (c) 2008 - 2025 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \version    
    \file       pairing.c
    \ingroup    pairing
    \brief      Pairing task
*/

#include "pairing.h"
#include "pairing_plugin.h"
#include "pairing_plugin_sm.h"
#include "pairing_config.h"
#include "pairing_event_send.h"
#include "pairing_protected.h"
#include "pairing_private.h"

#include "app_task.h"
#include "sdp.h"
#include "peer_signalling.h"
#include "system_state.h"
#include "adk_log.h"
#include "gatt_handler.h"
#include "device_properties.h"
#include "peer_link_keys.h"
#include "unexpected_message.h"

#include <device_db_serialiser.h>
#include <bredr_scan_manager.h>
#include <connection_manager.h>
#include <profile_manager.h>
#include <gatt.h>
#include <ui.h>

#include <panic.h>
#include <connection_abstraction.h>
#include <device.h>
#include <device_list.h>
#include <ps.h>
#include <string.h>
#include <stdio.h>
#include <logging.h>
#include <authentication.h>

/*! Check the message ranges are pemitted */
ASSERT_MESSAGE_GROUP_NOT_OVERFLOWED(PAIRING, PAIRING_MESSAGE_END)
ASSERT_INTERNAL_MESSAGES_NOT_OVERFLOWED(PAIRING_INTERNAL_MESSAGE_END)

/* Make the type used for message IDs available in debug tools */
LOGGING_PRESERVE_MESSAGE_ENUM(pairing_messages)
LOGGING_PRESERVE_MESSAGE_ENUM(pairing_internal_message_ids)
#include <connection.h>

#ifdef INCLUDE_MIRRORING
#include "peer_find_role.h"
#endif
#define AUTH_REQ_UNKNOWN        (HCI_MITM_PROTECTION_MAX + 1)

/*! Macro for simplifying creating messages */
#define MAKE_PAIRING_MESSAGE(TYPE) \
    TYPE##_T *message = PanicUnlessNew(TYPE##_T);

/*! Macro to make message with variable length for array fields. */
#define MAKE_PAIRING_MESSAGE_WITH_LEN(TYPE, LEN) \
    TYPE##_T *message = (TYPE##_T *) PanicUnlessMalloc(sizeof(TYPE##_T) + LEN);

#define PAIRING_IAC                     0x9E8B33

/*! The context id used by this module when calling CsrBtCmSmLeSecurityReqSend. */
#define PAIRING_CONTEXT 0x0000

#ifdef ENABLE_LE_DEBUG_SECONDARY
static pairing_override_cm_handler_callback_t override_cm_handler_cb = NULL;
#endif

/*!< pairing task */
pairingTaskData pairing_task_data;

static void pairing_HandleCmSmSimplePairingCompleteIndBle(const CsrBtCmSmSimplePairingCompleteInd *ind);

static void pairing_SendStopCfm(Task task, pairingStatus status)
{
    MAKE_PAIRING_MESSAGE(PAIRING_STOP_CFM);
    message->status = status;
    MessageSend(task, PAIRING_STOP_CFM, message);
}

static unsigned pairing_GetCurrentContext(void)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    pairing_provider_context_t context = context_handset_pairing_idle;

    if (thePairing->state_machine.state > PAIRING_STATE_IDLE)
    {
        context = context_handset_pairing_active;
    }
    return (unsigned)context;
}

static void pairing_ResetPendingBleAddress(void)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    
    BdaddrTypedSetEmpty(&thePairing->state_machine.device_address.taddr);
}

static void pairing_ResetIoCapsRcvdBdAddr(void)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    BdaddrSetZero(&thePairing->state_machine.device_address);
}

static uint8 pairing_ConvertToBlAuthReq(const CsrBtCmSmIoCapabilityRequestInd *ind,
                                        const pairing_io_capability_rsp_t *res)
{
    uint8 authReq = 0;

    /* If Security is handled by the security manager (BLE link) */
    if (ind->flags & DM_SM_FLAGS_SECURITY_MANAGER)
    {
        if (res->bonding)
        {
            authReq |= DM_SM_SECURITY_BONDING;
        }

        if (res->mitm)
        {
            authReq |= DM_SM_SECURITY_MITM_PROTECTION;
        }
        DEBUG_LOG("pairing_ConvertToBlAuthReq (DM_SM_FLAGS_SECURITY_MANAGER) - 0x%04x", authReq);
    }
    else /* Otherwise, security is being handled by the LM. */
    {
        if (res->bonding)
        {
            authReq = res->mitm ? HCI_MITM_REQUIRED_GENERAL_BONDING :
                                  HCI_MITM_NOT_REQUIRED_GENERAL_BONDING;
        }
        else
        {
            authReq = res->mitm ? HCI_MITM_REQUIRED_NO_BONDING :
                                  HCI_MITM_NOT_REQUIRED_NO_BONDING;
        }
        DEBUG_LOG("pairing_ConvertToBlAuthReq - 0x%04x", authReq);
    }

    if (ind->tp_addrt.tp_type == LE_ACL)
    {
        authReq |= DM_SM_SECURITY_SECURE_CONNECTIONS;
    }

    return authReq;
}

static uint8 pairing_ConvertToBlIoCap(cl_sm_io_capability ioCap)
{
    switch (ioCap)
    {
        case cl_sm_io_cap_display_only:
             return HCI_IO_CAP_DISPLAY_ONLY;

        case cl_sm_io_cap_display_yes_no:
            return HCI_IO_CAP_DISPLAY_YES_NO;

        case cl_sm_io_cap_keyboard_only:
            return HCI_IO_CAP_KEYBOARD_ONLY;

        case cl_sm_io_cap_no_input_no_output:
            return HCI_IO_CAP_NO_INPUT_NO_OUTPUT;

        case cl_sm_io_cap_keyboard_display:
            return HCI_IO_CAP_KEYBOARD_DISPLAY;

        default:
            return HCI_IO_CAP_NO_INPUT_NO_OUTPUT;
    }
}

static void pairing_RespondIoCapabilityRequest(const CsrBtCmSmIoCapabilityRequestInd *ind,
                                               const pairing_io_capability_rsp_t *res)
{
    uint8 authReq = pairing_ConvertToBlAuthReq(ind, res);

    if (res->io_capability == cl_sm_reject_request)
    {
        hci_error_t reason = (ind->tp_addrt.tp_type == LE_ACL) ? DM_SM_ERROR_PAIRING_NOT_SUPPORTED : HCI_ERROR_PAIRING_NOT_ALLOWED;

        CsrBtCmScDmIoCapabilityRequestNegRes(ind->tp_addrt.addrt.addr,
                                             ind->tp_addrt.addrt.type,
                                             ind->tp_addrt.tp_type,
                                             reason);
    }
    else
    {
        uint8 ioCap = pairing_ConvertToBlIoCap(res->io_capability);

        CsrBtCmScDmIoCapabilityRequestRes(ind->tp_addrt.addrt.addr,
                                          ind->tp_addrt.addrt.type,
                                          ind->tp_addrt.tp_type,
                                          ioCap,
                                          authReq,
                                          (CsrUint8) res->oob_data,
                                          res->oob_hash_c,
                                          res->oob_rand_r,
                                          res->key_distribution);
    }
}

/*! \brief Notify clients of pairing activity. 

    \param status The activity to notify. This should be from the #pairingActivityStatus enum.
    \param device_addr The Bluetooth address associated with the status. In most cases NULL.
    \param permanent Is the passed device_addr permanent.
*/
static void pairing_MsgActivity(pairingActivityStatus status, const bdaddr* device_addr, bool permanent, TRANSPORT_T transport)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    MAKE_PAIRING_MESSAGE(PAIRING_ACTIVITY);

    message->status = status;
    message->user_initiated = thePairing->pairing_clients[0].params.is_user_initiated;
    message->permanent = permanent;
    message->transport = transport;

    if (device_addr)
    {
        message->device_addr = *device_addr;
    }
    else
    {
        BdaddrSetZero(&message->device_addr);
    }

    TaskList_MessageSend(TaskList_GetFlexibleBaseTaskList(PairingGetPairingActivity()), PAIRING_ACTIVITY, message);
}

static void pairing_EirSetupCompleteCallback(bool success)
{
    DEBUG_LOG("pairing_EirSetupCompleteCallback success=%d", success);

    if(success)
    {
        MessageSend(SystemState_GetTransitionTask(), PAIRING_INIT_CFM, NULL);
        pairing_SetState(PairingGetTaskData(), PAIRING_STATE_IDLE);
    }
    else
    {
        Panic();
    }
}

static void pairing_EnterInitialising(pairingTaskData *thePairing)
{
    UNUSED(thePairing);

    DEBUG_LOG("pairing_EnterInitialising");

    ScanManager_ConfigureEirData(pairing_EirSetupCompleteCallback);

}

/*! @brief Actions to take when leaving #PAIRING_STATE_INITIALISING. */
static void pairing_ExitInitialising(pairingTaskData *thePairing)
{
    UNUSED(thePairing);

    DEBUG_LOG("pairing_ExitInitialising");
}

static void pairing_EnterIdle(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_EnterIdle");

    BredrScanManager_InquiryScanRelease(&thePairing->task);

    if (thePairing->pairing_clients[0].params.control_page_scan)
    {
        BredrScanManager_PageScanRelease(&thePairing->task);
    }

    if(thePairing->pairing_clients[0].stop_task)
    {
        pairing_SendStopCfm(thePairing->pairing_clients[0].stop_task, pairingSuccess);
        thePairing->pairing_clients[0].stop_task = NULL;
    }

    /* Disallow BLE pairing if there is no timeout guarding the BLE permission */
    if (!MessagePendingFirst(&thePairing->task, PAIRING_INTERNAL_LE_PAIR_TIMEOUT, NULL))
    {
        thePairing->state_machine.ble_permission = pairingBleDisallowed;
    }

    /* reset IO caps received handset address so allow pairing for new handset */
    pairing_ResetIoCapsRcvdBdAddr();

    /* Unlock pairing and permit a pairing operation */
    thePairing->pairing_lock = 0;
}

static void pairing_ExitIdle(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_ExitIdle");

    /* Lock pairing now that a pairing operation is underway */
    thePairing->pairing_lock = 1;
}

/*! \brief Restart pairing timeout
    \param timeout_s: Timeout in seconds
    \param client_task The task that initiated pairing by calling Pairing_Pair (or one of it's variants)
*/
static bool pairing_RestartTimeout(Task client_task, uint32 timeout_s)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    DEBUG_LOG("pairing_RestartTimeout");
    if(thePairing->pairing_clients[0].client_task == client_task)
    {
        MessageCancelFirst(&thePairing->task, PAIRING_INTERNAL_TIMEOUT_IND);
        MessageSendLater(&thePairing->task, PAIRING_INTERNAL_TIMEOUT_IND, 0, D_SEC(timeout_s));
    }
    else
    {
        DEBUG_LOG("pairing_RestartTimeout: failed to restart timeout, client task %p, thePairing task: %p\n", client_task, thePairing->pairing_clients[0].client_task);
        return FALSE;
    }
    return TRUE;
}

/*! \brief Restart pairing timeout using the value provided in the pairing parameters
    \param client_task The task that initiated pairing by calling Pairing_Pair (or one of it's variants)
*/
bool Pairing_RestartTimeout(Task client_task)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    return pairing_RestartTimeout(client_task, thePairing->pairing_clients[0].params.timeout_s);
}

/* Restart the pairing timeout to ensure it is always above the required minimum 30 second timeout */
static void pairing_RestartTimeoutOnPairingInitiated(pairingTaskData *thePairing)
{
    int32 remaining_timeout = 0;

    /* If there is a timeout pending this will get the time remaining, otherwise timeout will remain zero */
    MessagePendingFirst(&thePairing->task, PAIRING_INTERNAL_TIMEOUT_IND, &remaining_timeout);

    if(remaining_timeout < appConfigAuthenticationTimeout())
    {
        pairing_RestartTimeout(thePairing->pairing_clients[0].client_task, appConfigAuthenticationTimeout());
    }
}

static void pairing_EnterDiscoverable(pairingTaskData *thePairing)
{
    static const uint32 iac_array[] = { PAIRING_IAC };

    DEBUG_LOG("pairing_EnterHandsetDiscoverable");
    /* if pairing also requires BR/EDR */
    if(!thePairing->pairing_clients[0].params.le_only)
    {
        if (thePairing->pairing_clients[0].params.control_page_scan)
        {
            /* No longer idle, starting a pairing operation so need to be connectable */
            BredrScanManager_PageScanRequest(&thePairing->task, SCAN_MAN_PARAMS_TYPE_FAST);
        }

        ConnectionWriteInquiryAccessCode(&thePairing->task, iac_array, 1);

        /* Enable inquiry scan so handset can find us */
        BredrScanManager_InquiryScanRequest(&thePairing->task, SCAN_MAN_PARAMS_TYPE_FAST);

        /* Not initially pairing with BLE Handset */
        thePairing->pairing_clients[0].pair_le_task = NULL;
    }
    else
    {
        /* LE only pairing */
        thePairing->pairing_clients[0].pair_le_task = thePairing->pairing_clients[0].client_task;
    }

    /* Start pairing timeout */
    Pairing_RestartTimeout(thePairing->pairing_clients[0].client_task);

    /* Cancel any pending messages which disables ble pairing */
    MessageCancelAll(&thePairing->task, PAIRING_INTERNAL_LE_PAIR_TIMEOUT);

    /* When pairing enters discoverable mode set permission to AllowAll to faciliate br/edr pairing. */
    thePairing->state_machine.ble_permission = pairingBleAllowAll;

    /* Indicate pairing active */
    TaskList_MessageSendId(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), PAIRING_ACTIVE);

    /* notify clients that pairing is in progress */
    pairing_MsgActivity(pairingActivityInProgress, NULL, FALSE, TRANSPORT_NONE);
}

static void pairing_ExitDiscoverable(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_ExitDiscoverable");

    if(!thePairing->pairing_clients[0].params.le_only)
    {
        /* No longer discoverable, disable inquiry scan */
        BredrScanManager_InquiryScanRelease(&thePairing->task);
    }

    /* Cancel pairing timeout */
    MessageCancelFirst(&thePairing->task, PAIRING_INTERNAL_TIMEOUT_IND);

    /* Stop pairing indication */
    TaskList_MessageSendId(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), PAIRING_INACTIVE);
}

static void pairing_EnterAwaitingIoCapsExchange(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_EnterAwaitingIoCapsExchange");
    pairing_RestartTimeoutOnPairingInitiated(thePairing);
}

static void pairing_EnterIoCapsExchange(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_EnterIoCapsExchange");
    pairing_RestartTimeoutOnPairingInitiated(thePairing);
}

static void pairing_ExitAwaitingIoCapsExchange(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_ExitAwaitingIoCapsExchange");

    /* Cancel pairing timeout */
    MessageCancelAll(&thePairing->task, PAIRING_INTERNAL_TIMEOUT_IND);

    ConManagerUnregisterTpConnectionsObserver(cm_transport_ble, &thePairing->task);
}

static void pairing_ExitIoCapsExchange(pairingTaskData *thePairing)
{
    DEBUG_LOG("pairing_ExitIoCapsExchange");

    /* Cancel pairing timeout */
    MessageCancelAll(&thePairing->task, PAIRING_INTERNAL_TIMEOUT_IND);
}

void pairing_SetState(pairingTaskData *thePairing, pairingState state)
{
    DEBUG_LOG_STATE("pairing_SetState, Current State = enum:pairingState:%d, New State = enum:pairingState:%d",
                    thePairing->state_machine.state, state);

    if(thePairing->state_machine.state != state)
    {

        switch (thePairing->state_machine.state)
        {
            case PAIRING_STATE_INITIALISING:
                pairing_ExitInitialising(thePairing);
                break;

            case PAIRING_STATE_IDLE:
                pairing_ExitIdle(thePairing);
                break;

            case PAIRING_STATE_DISCOVERABLE:
                pairing_ExitDiscoverable(thePairing);
                break;

            case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
                pairing_ExitAwaitingIoCapsExchange(thePairing);
                break;

            case PAIRING_STATE_IO_CAPS_EXCHANGED:
                pairing_ExitIoCapsExchange(thePairing);
                break;

            default:
                break;
        }

        /* Set new state */
        thePairing->state_machine.state = state;

        /* Handle state entry functions */
        switch (state)
        {
            case PAIRING_STATE_INITIALISING:
                pairing_EnterInitialising(thePairing);
                break;

            case PAIRING_STATE_IDLE:
                pairing_EnterIdle(thePairing);
                break;

            case PAIRING_STATE_DISCOVERABLE:
                pairing_EnterDiscoverable(thePairing);
                break;

            case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
                pairing_EnterAwaitingIoCapsExchange(thePairing);
                break;

            case PAIRING_STATE_IO_CAPS_EXCHANGED:
                pairing_EnterIoCapsExchange(thePairing);
                break;

            default:
                break;
        }

        Ui_InformContextChange(ui_provider_handset_pairing, pairing_GetCurrentContext());
    }
    else /* thePairing->state == state */
    {
        if (thePairing->state_machine.state != PAIRING_STATE_IDLE)
        {
            Panic();
        }

        DEBUG_LOG_WARN("pairing_SetState setting PAIRING_STATE_IDLE state again - Ignored");
    }
}

pairingState pairing_GetState(pairingTaskData *thePairing)
{
    return thePairing->state_machine.state;
}

void pairing_ResetParams(pairing_params_t* params)
{
    PanicNull(params);
    memset(params, 0, sizeof(pairing_params_t));
    params->control_page_scan = TRUE;
    params->abort_on_failed_pairing = TRUE;
    params->timeout_s = appConfigAutoHandsetPairingTimeout();
}

static void pairing_SendNotReady(Task task)
{
    MAKE_PAIRING_MESSAGE(PAIRING_PAIR_CFM);
    message->status = pairingNotReady;
    MessageSend(task, PAIRING_PAIR_CFM, message);
}

static void pairing_Complete(pairingTaskData *thePairing, pairingStatus status, const bdaddr *bd_addr, bool permanent, TRANSPORT_T transport)
{
    DEBUG_LOG_INFO("pairing_Complete enum:pairingStatus:%d",status);

    if (thePairing->pairing_clients[0].client_task)
    {
        MAKE_PAIRING_MESSAGE(PAIRING_PAIR_CFM);
        if (bd_addr)
        {
            message->device_bd_addr = *bd_addr;
        }
        else
        {
            DeviceDbSerialiser_Serialise();
        }
        message->status = status;
        MessageSend(thePairing->pairing_clients[0].client_task, PAIRING_PAIR_CFM, message);
        /* Since the PAIRING_PAIR_CFM message has been sent to the client task
         * clear the task so that subsequent remote initiated pairing requests don't
         * get sent to the client */
        thePairing->pairing_clients[0].client_task = NULL;
    }

    if (pairingSuccess == status)
    {
        /* When peer pairing, the peer pair LE module decides when to indicate
           completion to clients as the LE pairing is not the completion of the
           peer pairing process. */
        if (thePairing->state_machine.device_type == DEVICE_TYPE_HANDSET)
        {
            if (thePairing->state_machine.smp_ctkd_expected)
            {
                /* if we expect CTKD soon, don't trigger the pairing-complete prompt yet,
                 * so that CTKD doesn't get held up by the prompt's chain-setup */
                DEBUG_LOG("pairing_Complete, CTKD expected, PAIRING_COMPLETE delayed for %u ms", appConfigSmpCtkdTimeout());
                MessageSendLater(&thePairing->task, PAIRING_INTERNAL_CTKD_TIMEOUT, NULL, appConfigSmpCtkdTimeout());
            }
            else
            {
                Pairing_SendPairingCompleteMessageToClients();
            }
        }
        /* notify clients that pairing succeeded */
        pairing_MsgActivity(pairingActivitySuccess, (bdaddr*)bd_addr, permanent, transport);
    }
    else
    {
        if (status != pairingStopped)
        {
            if(thePairing->pairing_clients[0].params.abort_on_failed_pairing)
            {
                TaskList_MessageSendId(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), PAIRING_FAILED);
            }
            else
            {
                if(status == pairingTimeout)
                {
                   TaskList_MessageSendId(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), PAIRING_FAILED);
                }
                else
                {
                   return;
                }
            }
        }

    }

    /* notify clients that handset pairing is not in progress */
    pairing_MsgActivity(pairingActivityNotInProgress, NULL, FALSE, TRANSPORT_NONE);

    /* Once pairing completed(regardless of the status) RESET is_user_initiated flag */
    thePairing->pairing_clients[0].params.is_user_initiated = FALSE;

    /* Move back to 'idle' state */
    pairing_SetState(thePairing, PAIRING_STATE_IDLE);
}


/*! \brief Set SCO forwarding supported features

    \param bd_addr Pointer to read-only device BT address to set featured for.
    \param supported_features Support featured for device.

static void pairing_SetScoForwardFeatures(const bdaddr *bd_addr, uint16 supported_features)
{
    device_t device = BtDevice_GetDeviceForBdAddr(bd_addr);
    if (device)
    {
        DEBUG_LOG("appDeviceSetScoForwardFeatures, features %04x", supported_features);
        Device_SetPropertyU16(device, device_property_sco_fwd_features, supported_features);
    }
}
*/
static void pairing_UpdateLinkMode(const bdaddr *bd_addr, cl_sm_link_key_type key_type)
{
    /* Check if the key_type generated is p256. If yes then set the
    * attribute.mode to sink_mode_unknown. Once the encryption type is known, device
    * attributes will be updated accordingly with proper mode.
    * Update the device attributes with this information to be reused later.
    */
    switch (key_type)
    {
        case cl_sm_link_key_unauthenticated_p256:
        case cl_sm_link_key_authenticated_p256:
            DEBUG_LOG("pairing_UpdateLinkMode: link_mode UNKNOWN");
            appDeviceSetLinkMode(bd_addr, DEVICE_LINK_MODE_UNKNOWN);
            break;

        default:
            DEBUG_LOG("pairing_UpdateLinkMode: link_mode NO_SECURE_CONNECTION");
            appDeviceSetLinkMode(bd_addr, DEVICE_LINK_MODE_NO_SECURE_CONNECTION);
            break;
    }
}

void pairing_ProcessSspCompleteInd(pairing_plugin_context_t context)
{
    pairing_ssp_complete_rsp_t bond_rsp;
    CsrBtCmSmSimplePairingCompleteInd ind;
    
    pairingTaskData *thePairing = PairingGetTaskData();
    DEBUG_LOG_INFO("pairing_ProcessSspCompleteInd enum:pairingState:%d", pairing_GetState(thePairing));

    if(Pairing_PluginSmGetSspCompleteInd(context, &ind))
    {
        tp_bdaddr tpaddr;
        BdaddrConvertTpBluestackToVm(&tpaddr, &ind.tp_addrt);
        
        DEBUG_LOG_INFO("pairing_ProcessSspCompleteInd enum:CsrBtTransportType:%d RPA=%d addr=%04x %02x %06x flags:0x%x status: 0x%x", 
            ind.tp_addrt.tp_type, ind.tp_addrt.addrt.type, ind.tp_addrt.addrt.addr.nap, ind.tp_addrt.addrt.addr.uap, ind.tp_addrt.addrt.addr.lap, ind.flags, ind.status);
        
        pairing_plugin_rsp_t response = Pairing_PluginSmSspCompleteIndicationCallbacks(context, &bond_rsp);
        
        DEBUG_LOG_INFO("pairing_ProcessSspCompleteInd enum:pairing_plugin_rsp_t:%d, enum:pairingBlePermission:%d", response, bond_rsp.ble_permission);
        
        if(response != pairing_plugin_response_wait)
        {
            if(ind.tp_addrt.tp_type == BREDR_ACL)
            {
                bdaddr* bredr_addr = &tpaddr.taddr.addr;
                
                switch (pairing_GetState(thePairing))
                {
                    case PAIRING_STATE_IO_CAPS_EXCHANGED:
                        if (ind.status != HCI_SUCCESS && !BdaddrIsSame(&thePairing->state_machine.device_address.taddr.addr, bredr_addr))
                        {
                            /* Getting here suggests that multiple AGs trying to pair to us.
                            Except first AG, all other AG's IO caps get rejected in IO_CAPS_EXCHANGED.
                            This results AG to abort the pairing and sending the authentication failure.
                            Just swallow the CFM (failure) message and wait for CFM (success) from AG
                            whose IO caps have been accepted. */
                            DEBUG_LOG_WARN("pairing_ProcessSspCompleteInd, authentication failed ignore");
                            break;
                        }
                    /* Fall-through */
                    case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
                        if (ind.status == HCI_SUCCESS)
                        {
                            CsrBtTdDbBredrKey bredrKey = { 0 };
                            
                            /* Update the device link mode based on the key type */
                            if (CsrBtTdDbGetBredrKey(ind.tp_addrt.addrt.type, &ind.tp_addrt.addrt.addr, &bredrKey) == CSR_BT_RESULT_CODE_TD_DB_SUCCESS)
                            {
                                DEBUG_LOG_INFO("pairing_ProcessSspCompleteInd, CsrBtTdDbGetBredrKey Success");
                                pairing_UpdateLinkMode(bredr_addr, bredrKey.linkkeyType);
                            }
                            else
                            {
                                DEBUG_LOG_ERROR("pairing_ProcessSspCompleteInd, CsrBtTdDbGetBredrKey Failed to retrive!");
                                Panic();
                            }

                            thePairing->state_machine.smp_ctkd_expected = FALSE;

                            /* Secure Connections is likely in use if the key-type is either of these,
                                * so expect CTKD to start soon */
                            if (bredrKey.linkkeyType == DM_SM_LINK_KEY_UNAUTHENTICATED_P256 ||
                                bredrKey.linkkeyType == DM_SM_LINK_KEY_AUTHENTICATED_P256)
                            {
                                thePairing->state_machine.smp_ctkd_expected = TRUE;
                            }

                            MessageSendLater(&thePairing->task,
                                                PAIRING_INTERNAL_LE_PAIR_TIMEOUT, NULL,
                                                D_SEC(appConfigLePairingDisableTimeout()));
                            
                            /* Send confirmation to main task */
                            pairing_Complete(thePairing, pairingSuccess, bredr_addr, TRUE, TRANSPORT_BREDR_ACL);
                            
                            Pairing_EventSendSimplePairingCompleteWithBdaddr(bredr_addr, ind.status);
                            
                        }
                        else
                        {
                            /* Send confirmation with error to main task */
                            pairing_Complete(thePairing, pairingAuthenticationFailed, bredr_addr, FALSE, TRANSPORT_NONE);
                            
                            Pairing_EventSendSimplePairingCompleteWithBdaddr(bredr_addr, ind.status);
                        }
                    break;

                    default:
                        if (ind.status == HCI_SUCCESS)
                        {
                            DEBUG_LOG_WARN("pairing_ProcessSspCompleteInd, unexpected bond confirm");
                        }
                    break;
                }
            }
            else
            {
                pairing_HandleCmSmSimplePairingCompleteIndBle(&ind);
            }
            
            thePairing->state_machine.ble_permission = bond_rsp.ble_permission;
            Pairing_PluginSmPairingComplete(&tpaddr);
        }
    }
}

/*! \brief Handle authentication confirmation

    The firmware has indicated that authentication with the remote device
    has completed, we should only receive this message in the pairing states.

    If we're pairing with a peer earbud, delete any previous pairing and then
    update the TWS+ service record with the address of the peer earbud.

    If we're pairing with a handset start SDP search to see if phone supports
    TWS+.
*/
static void pairing_SspCompleted(const CsrBtCmSmSimplePairingCompleteInd *ind)
{
    DEBUG_LOG_FN_ENTRY("pairing_SspCompleted");
    pairing_plugin_context_t context = Pairing_PluginSmSetSspCompleteInd(ind);
    pairing_ProcessSspCompleteInd(context);
}

/*! \brief Handle authorisation request for unknown device

    This function is called to handle an authorisation request for a device the
    connection library did not know about.

    If we are expecting a connection, notify the connection library if we accept
    or reject the connection.

    \note The response from the function is purely based on the state of the 
          pairing module. That is, TRUE is returned only if we are expecting
          a connection.

    \param[in] ind The authorise indication to process

    \return TRUE
*/
static void pairing_HandleCmSmAuthoriseInd(const CsrBtCmSmAuthoriseInd *ind)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    bool    handled = FALSE;
    bool    accepted = TRUE;
    bdaddr bd_addr;

    DEBUG_LOG("pairing_HandleCmSmAuthoriseInd, state %d, protocol %d, channel %d, incoming %d",
                   pairing_GetState(thePairing), ind->cs.connection.service.protocol_id, ind->cs.connection.service.channel, ind->cs.incoming);

    BdaddrConvertBluestackToVm(&bd_addr, &ind->cs.connection.addrt.addr);

    /*! \todo BLE: The pairing module doesn't handle LE connections at present */
    if (ind->cs.connection.service.protocol_id == SEC_PROTOCOL_LE_L2CAP)
    {
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Ignore. LE L2CAP");
    }
    else if ((ind->cs.connection.service.protocol_id == SEC_PROTOCOL_L2CAP) && (ind->cs.connection.service.channel == 1))
    {
        /* SDP L2CAP, always allow */
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Accept. SDP");

        handled = TRUE;
    }
    else if (appDeviceIsPeer(&bd_addr))
    {
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Ignore. Peer");

        /* Connection is to peer, so nothing to do with handset pairing. */
    }
    else if (pairing_GetState(thePairing) == PAIRING_STATE_DISCOVERABLE)
    {
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Accept. Not peer and we are looking for devices.");

        /* Pairing in progress and it is early enough to cancel it
         * cancel it and accept the connection */
        Pairing_PairStop(NULL);

        handled = TRUE;
    }
    else if (pairing_GetState(thePairing) == PAIRING_STATE_IO_CAPS_EXCHANGED &&
             BdaddrIsSame(&thePairing->state_machine.device_address.taddr.addr,  &bd_addr) &&
             BtDevice_isKnownBdAddr(&bd_addr))
    {
        /* Authorization is requested only after successful authentication,
         * so we must already be paired with this device.
         *
         * If we know this device already, and its address == device_address,
         * then CTKD is likely the only reason we're in this pairing state.
         *
         * We can allow upper layers to decide whether to authorise this connection or not. */
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Ignore while CTKD is ongoing");
    }
    else if (pairing_GetState(thePairing) == PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE ||
             pairing_GetState(thePairing) == PAIRING_STATE_IO_CAPS_EXCHANGED)
    {
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Reject. Awaiting pairing.");

        /* Pairing in progress, but too late to cancel the pairing,
         * refuse the connection */
        accepted = FALSE;
        handled = TRUE;
    }
    else
    {
        DEBUG_LOG("pairing_HandleCmSmAuthoriseInd Ignore (default action)");
    }

    if (handled)
    {
        CsrBtCmScDmAuthoriseRes(ind->cs.connection.addrt.addr,
                                ind->cs.incoming,
                                accepted,
                                ind->cs.connection.service.channel,
                                ind->cs.connection.service.protocol_id,
                                ind->cs.connection.addrt.type);
    }
    else
    { /* Connection_manager must handle the indication */
        ConManagerHandleDmSmAuthoriseInd(ind);
    }
}

void pairing_ProcessIoCapabilityRequestInd(pairing_plugin_context_t context)
{
    CsrBtCmSmIoCapabilityRequestInd ind;
    pairingTaskData *thePairing = PairingGetTaskData();
    DEBUG_LOG_INFO("pairing_ProcessIoCapabilityRequestInd enum:pairingState:%d", pairing_GetState(thePairing));
    
    if(Pairing_PluginSmGetIoCapabilityRequestInd(context, &ind))
    {
        tp_bdaddr tpaddr;
        pairing_io_capability_rsp_t io_capability_rsp;
        bool sm_over_bredr = ind.flags & DM_SM_FLAGS_SMP_OVER_BREDR;

        BdaddrConvertTpBluestackToVm(&tpaddr, &ind.tp_addrt);
        
        DEBUG_LOG_INFO("pairing_ProcessIoCapabilityRequestInd enum:CsrBtTransportType:%d RPA=%d addr=%04x %02x %06x flags:0x%x", 
            ind.tp_addrt.tp_type, ind.tp_addrt.addrt.type, ind.tp_addrt.addrt.addr.nap, ind.tp_addrt.addrt.addr.uap, ind.tp_addrt.addrt.addr.lap, ind.flags);
        
        pairing_plugin_rsp_t response = Pairing_PluginSmIoCapabilityRequestCallbacks(context, &io_capability_rsp);
        
        DEBUG_LOG_INFO("pairing_ProcessIoCapabilityRequestInd enum:pairing_plugin_rsp_t:%d, enum:cl_sm_io_capability:%d, enum:mitm_setting:%d, bonding %d, key_distribution %d, oob_data %d", 
            response, io_capability_rsp.io_capability, io_capability_rsp.mitm, io_capability_rsp.bonding, io_capability_rsp.key_distribution, io_capability_rsp.oob_data);
            
        if(response == pairing_plugin_response_wait)
        {
            /* One of the plugins requested to wait for a decision */
            return;
        }
        
        /* Make state updates based on the final response */
        if(tpaddr.transport == TRANSPORT_BLE_ACL)
        {
            /* Clear expected address for BLE pairing. We should not have a crossover
               but in any case, safest to remove any pending */
            pairing_ResetPendingBleAddress();
            
            if(io_capability_rsp.io_capability == cl_sm_reject_request)
            {
                if(pairing_GetState(thePairing) == PAIRING_STATE_IDLE || pairing_GetState(thePairing) == PAIRING_STATE_DISCOVERABLE)
                {
                    /* 
                    If the pairing state is not moved to PAIRING_STATE_IO_CAPS_EXCHANGED, pairing state would still
                    be in IDLE state and when CL_SM_BLE_SIMPLE_PAIRING_COMPLETE_IND_T is received later, pairing_Complete is 
                    called with pairing_failed as status and again the pairing state will be moved to IDLE leading to a panic.
                    */
                    pairing_SetState(thePairing, PAIRING_STATE_IO_CAPS_EXCHANGED);
                }
            }
            else
            {
                if(pairing_GetState(thePairing) == PAIRING_STATE_IDLE || pairing_GetState(thePairing) == PAIRING_STATE_DISCOVERABLE)
                {
                    if (sm_over_bredr)
                    {
                        /* CTKD has started, allow it to complete before informing clients of pairing result */
                        MessageCancelAll(&thePairing->task, PAIRING_INTERNAL_CTKD_TIMEOUT);

                        /* If its known address and SM over BR/EDR then CTKD has started */
                        thePairing->state_machine.smp_ctkd_expected = FALSE; /* no more expectation, it actually happening */
                        thePairing->state_machine.smp_ctkd_ongoing = TRUE;
                    }
                    
                    /* Pairing started locally, IO caps from AG is received
                       wait for authentication in IO_CAPS_EXCHANGED state.*/
                    pairing_SetState(thePairing, PAIRING_STATE_IO_CAPS_EXCHANGED);
                }
                
                thePairing->state_machine.device_address = tpaddr;
            }
        }
        
        if(io_capability_rsp.io_capability != cl_sm_reject_request)
        {
            if(tpaddr.transport == TRANSPORT_BREDR_ACL)
            {
                if((pairing_GetState(thePairing) == PAIRING_STATE_DISCOVERABLE) || (pairing_GetState(thePairing) == PAIRING_STATE_IDLE))
                {
                    thePairing->state_machine.device_address = tpaddr;

                    /* Pairing started locally, IO caps from AG is received
                    wait for authentication in IO_CAPS_EXCHANGED state.*/
                    pairing_SetState(thePairing, PAIRING_STATE_IO_CAPS_EXCHANGED);
                }
            }
        }

        DEBUG_LOG("pairing_ProcessIoCapabilityRequestInd, accept %d device_address %04x,%02x,%06lx",
                    (io_capability_rsp.io_capability != cl_sm_reject_request),
                    thePairing->state_machine.device_address.taddr.addr.nap,
                    thePairing->state_machine.device_address.taddr.addr.uap,
                    thePairing->state_machine.device_address.taddr.addr.lap);

        pairing_RespondIoCapabilityRequest(&ind, &io_capability_rsp);
    }
}

/*! \brief Handle IO capabilities request

    This function is called when the remote device wants to know the devices IO
    capabilities.  If we are pairing we respond indicating the headset has no input
    or ouput capabilities, if we're not pairing then just reject the request.
*/
static void pairing_HandleCmSmIoCapabilityRequestInd(const CsrBtCmSmIoCapabilityRequestInd *ind)
{
    DEBUG_LOG_FN_ENTRY("pairing_HandleCmSmIoCapabilityRequestInd");
    pairing_plugin_context_t context = Pairing_PluginSmSetIoCapabilityRequestInd(ind);
    pairing_ProcessIoCapabilityRequestInd(context);
}

static void pairing_HandleCmSmIoCapabilityResponseInd(const CsrBtCmSmIoCapabilityResponseInd *ind)
{
    DEBUG_LOG_INFO("pairing_HandleCmSmIoCapabilityResponseInd %lx auth:%x io:%x oob:%x", 
        ind->tp_addrt.addrt.addr.lap, ind->authentication_requirements, ind->io_capability, ind->oob_data_present);

    pairing_plugin_context_t context = Pairing_PluginSmSetRemoteIoCapability(ind);
    Pairing_PluginSmRemoteIoCapabilityCallbacks(context);
}

/*! \brief Handle request to start pairing with a device. */
static void pairing_HandleInternalPairRequest(pairingTaskData *thePairing, PAIR_REQ_T *req)
{
    DEBUG_LOG("pairing_HandleInternalPairRequest, state %d", pairing_GetState(thePairing));

    switch (pairing_GetState(thePairing))
    {
        case PAIRING_STATE_IDLE:
        {
            /* Store client task */
            thePairing->pairing_clients[0].client_task = req->client_task;

            thePairing->pairing_clients[0].params = req->params;

            thePairing->state_machine.device_type = DEVICE_TYPE_HANDSET;

            /* no address, go discoverable for inquiry process */
            if (BdaddrIsZero(&req->params.addr))
            {
                /* Move to 'discoverable' state to start inquiry & page scan */
                pairing_SetState(thePairing, PAIRING_STATE_DISCOVERABLE);
            }
            else
            {
                ConnectionSmAuthenticate(appGetAppTask(), &req->params.addr, appConfigAuthenticationTimeout());
                pairing_SetState(thePairing, PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE);
            }
        }
        break;

        default:
        {
            pairing_SendNotReady(req->client_task);
        }
        break;
    }
}

static void pairing_HandleInternalPairLePeerRequest(pairingTaskData *thePairing, PAIR_LE_PEER_REQ_T *req)
{
    DEBUG_LOG("pairing_HandleInternalPairLePeerRequest, state %d server %d", pairing_GetState(thePairing), req->le_peer_server);

    if(pairing_GetState(thePairing) == PAIRING_STATE_IDLE)
    {
        /* Store client task */
        thePairing->pairing_clients[0].client_task = req->client_task;
        pairing_ResetParams(&thePairing->pairing_clients[0].params);

        thePairing->state_machine.device_address.taddr = req->typed_addr;

        thePairing->pairing_lock = TRUE;

        thePairing->state_machine.device_type = DEVICE_TYPE_EARBUD;
        /* Set ble pairing permission to AllowAll so that peers can pair with each other. */
        thePairing->state_machine.ble_permission = pairingBleAllowAll;
        if(req->le_peer_server)
        {
            CsrBtTypedAddr addrt;
            BdaddrConvertTypedVmToBluestack(&addrt, &req->typed_addr);
            CsrBtCmSmLeSecurityReqSend(addrt,
                                       L2CA_CONFLAG_ENUM(L2CA_CONNECTION_LE_SLAVE_DIRECTED),
                                       PAIRING_CONTEXT,
                                       DM_SM_SECURITY_AUTHENTICATED_BONDING);
        }
        else
        {
            /* Detect a disconnect. The other branch of this if calls into library
               functions that should fail in case of a connection loss */
            ConManagerRegisterTpConnectionsObserver(cm_transport_ble, &thePairing->task);
        }
        pairing_SetState(thePairing, PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE);
    }
    else
    {
        pairing_SendNotReady(req->client_task);
    }
}

static void pairing_HandleInternalTimeoutIndications(pairingTaskData *thePairing)
{
    pairingState state = pairing_GetState(thePairing);

    DEBUG_LOG("pairing_HandleInternalTimeoutIndications state enum:pairingState:%d",
              state);

    switch (state)
    {
        case PAIRING_STATE_DISCOVERABLE:
        case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
        case PAIRING_STATE_IO_CAPS_EXCHANGED:
            /* Disable LE pairing when pairing gets timed out */
            thePairing->state_machine.ble_permission = pairingBleDisallowed;
            /* Send confirmation with error to main task */
            pairing_Complete(thePairing, pairingTimeout, NULL, FALSE, TRANSPORT_NONE);
            
            Pairing_EventSendSimplePairingCompleteWithBdaddr(NULL, hci_error_host_timeout);
            break;

        default:
            break;
    }
}

static void pairing_HandleInternalDisconnectIndication(pairingTaskData *thePairing)
{
    pairingState state = pairing_GetState(thePairing);

    DEBUG_LOG("pairing_HandleInternalDisconnectIndication state enum:pairingState:%d",
              state);

    switch (state)
    {
        case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
        case PAIRING_STATE_IO_CAPS_EXCHANGED:
            /* Send confirmation with error to main task */
            pairing_Complete(thePairing, pairingFailed, NULL, FALSE, TRANSPORT_NONE);
            Pairing_EventSendSimplePairingCompleteWithBdaddr(NULL, hci_error_auth_fail);
            break;

        default:
            break;
    }
}

/*! \brief Handle request to stop pairing. */
static void pairing_HandleInternalPairStopReq(pairingTaskData* thePairing, PAIRING_INTERNAL_PAIR_STOP_REQ_T* req)
{
    pairingState state = pairing_GetState(thePairing);
    DEBUG_LOG("pairing_HandleInternalPairStopReq enum:pairingState:%d", state);

    switch(state)
    {
        case PAIRING_STATE_IDLE:
            if(req->client_task)
            {
                pairing_SendStopCfm(req->client_task, pairingSuccess);
            }
            break;

        case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
        case PAIRING_STATE_IO_CAPS_EXCHANGED:
            /* Pairing already in progress. Cannot be stopped by user.
             * Either pairing completes or gets timed out */
            if(req->client_task)
            {
                pairing_SendStopCfm(req->client_task, pairingInProgress);
            }
            break;

        case PAIRING_STATE_DISCOVERABLE:
            /* Disable LE pairing when pairing is cancelled */
            thePairing->state_machine.ble_permission = pairingBleDisallowed;
            if(thePairing->pairing_clients[0].stop_task)
            {
                Panic();
            }

            thePairing->pairing_clients[0].stop_task = req->client_task;

            /* just send complete message with stopped status, there is an auto
            * transition back to idle after sending the message */
            pairing_Complete(thePairing, pairingStopped, NULL, FALSE, TRANSPORT_NONE);
            Pairing_EventSendSimplePairingCompleteWithBdaddr(NULL, hci_error_operation_cancelled_by_host);
            break;

        default:
            break;
    }
}

static void pairing_HandleTpDisconnectInd(const CON_MANAGER_TP_DISCONNECT_IND_T *ind)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG_FN_ENTRY("pairing_HandleTpDisconnectInd for 0x%06lx",
                        ind->tpaddr.taddr.addr.lap);

    if (BdaddrTypedIsSame(&thePairing->state_machine.device_address.taddr, &ind->tpaddr.taddr))
    {
        MessageSend(&thePairing->task, PAIRING_INTERNAL_DISCONNECT_IND, NULL);
    }
}

/*! \brief Handle confirmation of adding link key for handset to PDL
 */
static void pairing_HandleCmDatabaseCfmWrite(const CsrBtCmDatabaseCfm *cfm)
{
    bdaddr bd_addr;

    BdaddrConvertBluestackToVm(&bd_addr, &cfm->deviceAddr);

   /* Set event indicating we've received a handset link-key.
      Can be used by client to decide to connect to the handset. */
    pairing_MsgActivity(pairingActivityLinkKeyReceived, NULL, FALSE, TRANSPORT_NONE);

    if (cfm->resultCode != CSR_BT_RESULT_CODE_CM_SUCCESS)
    {
        DEBUG_LOG("pairing_HandleCmDatabaseCfmWrite result:0x%04x supplier:0x%04x",
                  cfm->resultCode, cfm->resultSupplier);
    }

    /* Tell the peer that the link key has been set ok */
    PeerLinkKeys_SendKeyResponseToPeer(&bd_addr, (cfm->resultCode == CSR_BT_RESULT_CODE_CM_SUCCESS));
}

/*  Handle Pin code indication

    The firmware has indicated that it requires the pin code,
    pin code pairing is not supported.
*/
static void pairing_HandleCmSmPinRequestInd(const CsrBtCmSmPinRequestInd *ind)
{
    DEBUG_LOG("pairing_HandleCmSmPinRequestInd - not supported");

    /* Respond to the PIN code request with empty pin code */
    CsrBtCmScDmPinRequestNegRes(ind->addrt.addr);
}

void pairing_ProcessUserConfirmationInd(pairing_plugin_context_t context)
{
    CsrBtCmSmUserConfirmationRequestInd ind;
    DEBUG_LOG_INFO("pairing_ProcessUserConfirmationInd enum:pairingState:%d", pairing_GetState(PairingGetTaskData()));
    
    if(Pairing_PluginSmGetUserConfirmationInd(context, &ind))
    {
        pairing_user_confirmation_rsp_t user_confirmation_rsp = {0};
        
        DEBUG_LOG_INFO("pairing_ProcessUserConfirmationInd enum:CsrBtTransportType:%d RPA=%d addr=%04x %02x %06x flags:0x%x", 
            ind.tp_addrt.tp_type, ind.tp_addrt.addrt.type, ind.tp_addrt.addrt.addr.nap, ind.tp_addrt.addrt.addr.uap, ind.tp_addrt.addrt.addr.lap, ind.flags);
        
        pairing_plugin_rsp_t response = Pairing_PluginSmUserConfirmationRequestCallbacks(context, &user_confirmation_rsp);
        
        DEBUG_LOG_INFO("pairing_ProcessUserConfirmationInd enum:pairing_plugin_rsp_t:%d, accept=%d", response, user_confirmation_rsp.accept);

        if(response != pairing_plugin_response_wait)
        {
            if (user_confirmation_rsp.accept)
            {
                CsrBtCmScDmUserConfirmationRequestRes(ind.tp_addrt.addrt.addr,
                                                      ind.tp_addrt.addrt.type,
                                                      ind.tp_addrt.tp_type);
            }
            else
            {
                CsrBtCmScDmUserConfirmationRequestNegRes(ind.tp_addrt.addrt.addr,
                                                         ind.tp_addrt.addrt.type,
                                                         ind.tp_addrt.tp_type);
            }
        }
    }
}

/*  Handle the user passkey confirmation.

    This function is called to handle confirmation for the user that the passkey
    matches, since the headset doesn't have a display we just send a reject
    immediately.
*/
static void pairing_HandleCmSmUserConfirmationRequestInd(const CsrBtCmSmUserConfirmationRequestInd *ind)
{
    
    DEBUG_LOG_FN_ENTRY("pairing_HandleCmSmUserConfirmationRequestInd");
    pairing_plugin_context_t context = Pairing_PluginSmSetUserConfirmationInd(ind);
    pairing_ProcessUserConfirmationInd(context);
}


/*! \brief Handler for CSR_BT_CM_SM_USER_PASSKEY_REQUEST_IND.
*/
static void pairing_HandleCmSmUserPasskeyRequestInd(const CsrBtCmSmUserPasskeyRequestInd *ind)
{
    DEBUG_LOG("pairing_HandleCmSmUserPasskeyRequestInd");

    CsrBtCmScDmUserPasskeyRequestNegRes(ind->tp_addrt.addrt.addr,
                                        ind->tp_addrt.addrt.type,
                                        ind->tp_addrt.tp_type);
}

/*! \brief Send PAIRING_PAIR_CFM to the LE task which requested for pairing */
static void pairing_SendPairCfmToLeTask(bdaddr address, pairingStatus pairing_status)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    /*  Indicate pairing status to client and remain in Discoverable state  */
    MAKE_PAIRING_MESSAGE(PAIRING_PAIR_CFM);

    message->status = pairing_status;

    message->device_bd_addr = address;
    MessageSend(thePairing->pairing_clients[0].pair_le_task, PAIRING_PAIR_CFM, message);

    thePairing->pairing_clients[0].pair_le_task = NULL;
    thePairing->state_machine.ble_permission = pairingBleDisallowed;
}

/*! \brief Handler for CSR_BT_CM_SM_SIMPLE_PAIRING_COMPLETE_IND. */
static void pairing_HandleCmSmSimplePairingCompleteIndBle(const CsrBtCmSmSimplePairingCompleteInd *ind)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    tp_bdaddr tpaddr;
    typed_bdaddr permanentAddr;
    tp_bdaddr event_tpaddr;

    BdaddrConvertTpBluestackToVm(&tpaddr, &ind->tp_addrt);

    bool current_request = BdaddrTypedIsSame(&thePairing->state_machine.device_address.taddr, &tpaddr.taddr);
    bool any_pending     = !BdaddrTypedIsEmpty(&thePairing->state_machine.device_address.taddr);
    bool permanent       = BtDevice_GetPublicAddress(&tpaddr.taddr, &permanentAddr);
    device_t device      = BtDevice_GetDeviceForBdAddr(&permanentAddr.addr);
    pairingBlePermission permission = thePairing->state_machine.ble_permission;
    pairingStatus pairing_status    = pairingFailed;

    DEBUG_LOG_INFO("pairing_HandleCmSmSimplePairingCompleteIndBle Any:%d Matches:%d Permission %d flags 0x%x, status = %x",
                        any_pending, current_request, permission, ind->flags, ind->status);

    if (any_pending && !current_request)
    {
        PanicFalse(ind->status != HCI_SUCCESS);
        DEBUG_LOG_ERROR("pairing_HandleCmSmSimplePairingCompleteIndBle, ignore SSP-complete from an unexpected device, lap=%06x", tpaddr.taddr.addr.lap);
        return;
    }

    if (device != NULL)
    {
        DEBUG_LOG("pairing_HandleCmSmSimplePairingCompleteIndBle Device type = %x",  BtDevice_GetDeviceType(device));
    }

    if (current_request && thePairing->pairing_clients[0].pair_le_task != NULL)
    {
        DEBUG_LOG_INFO("pairing_HandleCmSmSimplePairingCompleteIndBle: LE handset status %d", ind->status);

        if (ind->status == HCI_SUCCESS)
        {
            pairing_status = pairingSuccess;
        }
        else
        {
            pairing_status = pairingFailed;
        }

        pairing_SendPairCfmToLeTask(tpaddr.taddr.addr, pairing_status);
    }
    else
    {
        if (any_pending && current_request && permission > pairingBleDisallowed)
        {
            CsrBtTypedAddr addrt;

            if (permanent)
            {
                BdaddrConvertTypedVmToBluestack(&addrt, &permanentAddr);
            }
            else
            {
                addrt = ind->tp_addrt.addrt;
            }

            bool paired = CsrBtTdDbDeviceExists(addrt.type, &addrt.addr);

            /* Based on the permission allowed with the peer device, decide on the connection */
            bool permitted = (permission == pairingBleOnlyPairedDevices && (paired)) ||
                             (permission == pairingBleAllowOnlyResolvable && permanent) ||
                             (permission == pairingBleAllowAll);

            DEBUG_LOG_INFO("pairing_HandleCmSmSimplePairingCompleteIndBle, Permanent:%d Permitted:%d, Paired = %d",
                        permanent, permitted, paired);

            if (!permitted)
            {
                /* CTKD didn't go well. But we are still bonded with handset over BR/EDR
                   Lets not delete the device info. If the handset forgets us then we need to anyways
                   do fresh pairing
                 */
                if(!thePairing->state_machine.smp_ctkd_ongoing)
                {
                    /* Disconnect the link and delete device keys */
                    DEBUG_LOG("pairing_HandleCmSmSimplePairingCompleteIndBle Pairing not permitted, disconnecting lap=%06x",tpaddr.taddr.addr.lap);
                    ConManagerReleaseTpAcl(&tpaddr);
                    CmScDmRemoveDeviceReq(addrt.addr, addrt.type);
                }
            }

            /* Pairing is accepted if status is HCI_SUCCESS. */
            if (ind->status == HCI_SUCCESS && permitted)
            {
                pairing_status = pairingSuccess;
            }
            pairing_ResetPendingBleAddress();
        }
    }

    /* Whatever is the case, we are done with CTKD (if one was going on)*/
    thePairing->state_machine.smp_ctkd_ongoing = FALSE;

    pairing_Complete(thePairing,
                     pairing_status,
                     permanent ? &permanentAddr.addr : &tpaddr.taddr.addr,
                     permanent,
                     TRANSPORT_BLE_ACL);
    
    event_tpaddr.transport = TRANSPORT_BLE_ACL;
    if (permanent)
    {
        event_tpaddr.taddr = permanentAddr;
    }
    else
    {
        event_tpaddr = tpaddr;
    }
    
    Pairing_EventSendSimplePairingCompleteWithTpaddr(&event_tpaddr, ind->status);
    
    if (pairing_status == pairingSuccess)
    {
        Pairing_EventSendBleBond(&event_tpaddr);
    }
}

static void pairing_HandleCmEncryptChangeInd(const CsrBtCmEncryptChangeInd *ind)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("pairing_HandleCmEncryptChangeInd - Address %x EncryptType:%d",
              ind->deviceAddr.lap,
              ind->encryptType);

    /* Since authentication module doesn't have any task associated with it,
     * we are calling the encryption change handling from pairing. */
    appAuthHandleCmEncryptionChangeInd(&thePairing->task, ind);
    
    if (ind->transportType == LE_ACL)
    {
        tp_bdaddr tpaddr;
        tpaddr.transport = TRANSPORT_BLE_ACL;
        tpaddr.taddr.type = ind->deviceAddrType;
        BdaddrConvertBluestackToVm(&tpaddr.taddr.addr, &ind->deviceAddr);
        Pairing_EventSendBleEncryptionChanged(&tpaddr);
    }
}

static void pairing_HandleCmSmSecurityInd(const CsrBtCmSmSecurityInd *ind)
{
    DEBUG_LOG("pairing_HandleCmSmSecurityInd. Link security(sc?):%d x%04x",
                        (ind->security_requirements & DM_SM_SECURITY_SECURE_CONNECTIONS), ind->addrt.addr.lap);
}

static void pairing_SendSecurityCfmEvent(const CsrBtCmSmSecurityCfm *cfm)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    MAKE_PAIRING_MESSAGE(PAIRING_SECURITY_CFM);

    message->hci_status = cfm->status;
    message->context = cfm->context;
    BdaddrConvertTypedBluestackToVm(&message->typed_addr, &cfm->addrt);
    TaskList_MessageSend(thePairing->security_client_tasks, PAIRING_SECURITY_CFM, message);
}

static void pairing_HandleCmSmSecurityCfm(const CsrBtCmSmSecurityCfm *cfm)
{
    typed_bdaddr typed_addr;
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("pairing_HandleCmSmSecurityCfm: state enum:pairingState:%u status 0x%x lap %06x context %u",
              PairingGetTaskData()->state_machine.state,
              cfm->status, cfm->addrt.addr.lap, cfm->context);

    switch (cfm->context)
    {
    case PAIRING_CONTEXT:
        {
            switch (cfm->status)
            {
            case HCI_SUCCESS:
                {
                    DEBUG_LOG("**** ble_security_success");

                    /* Check if CSR_BT_CM_SM_SECURITY_CFM is received while in AWAITING_IO_CAPS_EXCHANGE state or not. This is not expected to happen in normal pairing
                       scenarios as we will be receiving simple pairing indication first and there we would have exited this state.
                       However in scenarios like, if its a locally initiated pairing and pairing device is an already paired device, we will not get simple pairing
                       indication. In such cases, we need to ensure we exits pairing state when we get CSR_BT_CM_SM_SECURITY_CFM. */
                    if (thePairing->state_machine.state == PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE ||
                        thePairing->state_machine.state == PAIRING_STATE_IO_CAPS_EXCHANGED)
                    {
                        BdaddrConvertTypedBluestackToVm(&typed_addr, &cfm->addrt);

                        /* Send PAIRING_PAIR_CFM if the received security confirm is from the device which is intended to get paired */
                        if (BdaddrTypedIsSame(&thePairing->state_machine.device_address.taddr, &typed_addr) && thePairing->pairing_clients[0].pair_le_task != NULL)
                        {
                            pairing_SendPairCfmToLeTask(typed_addr.addr, pairingSuccess);

                            /* Whatever is the case, we are done with CTKD (if one was going on)*/
                            thePairing->state_machine.smp_ctkd_ongoing = FALSE;

                            pairing_Complete(thePairing, pairingSuccess, &typed_addr.addr, FALSE, TRANSPORT_BLE_ACL);
                        }
                    }
                }
                break;

            case HCI_ERROR_HOST_BUSY_PAIRING:
                DEBUG_LOG("**** ble_security_pairing_in_progress");
                break;

            case HCI_ERROR_KEY_MISSING:
                DEBUG_LOG("**** ble_security_link_key_missing");
                break;

            default:
                {
                    DEBUG_LOG("**** ble_security_fail PAIRING FAILED");

                    switch (PairingGetTaskData()->state_machine.state)
                    {
                    case PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE:
                    case PAIRING_STATE_IO_CAPS_EXCHANGED:
                        {
                            tp_bdaddr event_tpaddr;

                            BdaddrConvertTypedBluestackToVm(&typed_addr, &cfm->addrt);
                            pairing_Complete(PairingGetTaskData(), pairingAuthenticationFailed, &typed_addr.addr, FALSE, TRANSPORT_NONE);
                            
                            event_tpaddr.transport = TRANSPORT_BLE_ACL;
                            event_tpaddr.taddr = typed_addr;
        
                            Pairing_EventSendSimplePairingCompleteWithTpaddr(&event_tpaddr, cfm->status);
                        }
                        break;

                    default:
                        break;
                    }
                }
                break;
            }
        }
        break;

#ifdef INCLUDE_MIRRORING
    case PEER_FIND_ROLE_CONTEXT:
        peer_find_role_handle_dm_sm_security_cfm(cfm->status);
        break;
#endif

    default:
        break;
    }

    pairing_SendSecurityCfmEvent(cfm);
}

static void pairing_HandleCmSmBondingCfm(const CsrBtCmSmBondingCfm *cfm)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("pairing_HandleCmSmBondingCfm, state %d, status %d, flags %d",
              pairing_GetState(thePairing),
              cfm->status,
              cfm->flags);

    /* For BREDR transport, pairing is considered successful when bonding completes i.e. when we receive security keys.
       This is indicated by CSR_BT_CM_SECURITY_EVENT_IND and hence pairing_SspCompleted is called on receiving this indication in case of successful pairing. 
       In case of pairing failure, security keys and CSR_BT_CM_SECURITY_EVENT_IND are not received.
       Hence, the pairing failure is handled on receiving respective pairing completion indications i.e. 
       CSR_BT_CM_SM_BONDING_CFM for locally initiated pairing and CSR_BT_CM_SM_SIMPLE_PAIRING_COMPLETE_IND for remotely initiated pairing.
     */   
    if (cfm->status != HCI_SUCCESS)
    {
        CsrBtCmSmSimplePairingCompleteInd ind;
        ind.type = CSR_BT_CM_SM_SIMPLE_PAIRING_COMPLETE_IND;
        ind.phandle = 0;
        ind.status = cfm->status;
        ind.tp_addrt.tp_type = BREDR_ACL;
        ind.tp_addrt.addrt = cfm->addrt;
        ind.flags = cfm->flags;
        pairing_SspCompleted(&ind);
    }
}

static void pairing_HandleCmSmSimplePairingCompleteInd(const CsrBtCmSmSimplePairingCompleteInd *ind)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("pairing_HandleCmSmSimplePairingCompleteInd, state=enum:pairingState:%d status=%d, flags=%d type=enum:PHYSICAL_TRANSPORT_T:%d",
              pairing_GetState(thePairing),
              ind->status,
              ind->flags,
              ind->tp_addrt.tp_type);

    if (ind->tp_addrt.tp_type == BREDR_ACL)
    {
        if (ind->flags & DM_SM_FLAGS_INITIATOR)
        {
            /* Locally initiated, concluded on receiving DM_SM_BONDING_CFM */
        }
        else
        {
            /* For BREDR transport, pairing is considered successful when bonding completes i.e. when we receive security keys.
               This is indicated by CSR_BT_CM_SECURITY_EVENT_IND and hence pairing_SspCompleted is called on receiving this indication in case of successful pairing. 
               In case of pairing failure, security keys and CSR_BT_CM_SECURITY_EVENT_IND are not received.
               Hence, the pairing failure is handled on receiving respective pairing completion indications i.e. 
               CSR_BT_CM_SM_BONDING_CFM for locally initiated pairing and CSR_BT_CM_SM_SIMPLE_PAIRING_COMPLETE_IND for remotely initiated pairing.
             */   
            if (ind->status != HCI_SUCCESS)
            {
                pairing_SspCompleted(ind);
            }
        }
    }
    else
    {
        pairing_SspCompleted(ind);
    }
}

static void pairing_HandleCmSecurityEventInd(const CsrBtCmSecurityEventInd *security_ind)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("pairing_HandleCmSecurityEventInd, state %d, transportMask %d, event %d",
              pairing_GetState(thePairing),
              security_ind->transportMask,
              security_ind->event);

    /* Key distribution is completed, and this marks the end of successful BR/EDR pairing on the BR/EDR transport.
        In contrast, BR/EDR pairing due to CTKD is considered complete, only after the LE SSP Complete indication. 
        
        The condition below checks that :
        - the key generated is for BR/EDR
        - the security event is BOND
        - the generated using the BR/EDR transport and not CTKD over BLE
    */
    if (security_ind->transportMask == CSR_BT_TRANSPORT_TYPE_FLAG_BREDR && 
        security_ind->event == CSR_BT_CM_SECURITY_EVENT_BOND &&
        thePairing->state_machine.device_address.transport == TRANSPORT_BREDR_ACL)
    {
        CsrBtCmSmSimplePairingCompleteInd ind;
        ind.type = CSR_BT_CM_SM_SIMPLE_PAIRING_COMPLETE_IND;
        ind.phandle = 0;
        ind.status = HCI_SUCCESS;
        ind.tp_addrt.tp_type = BREDR_ACL;
        ind.tp_addrt.addrt = security_ind->addrt;
        ind.flags = 0;
        pairing_SspCompleted(&ind);
    }
    
}

/*! \brief Use link key for attached device to derive key for peer earbud.
 */
static void pairing_HandleCmDatabaseCfm(const CsrBtCmDatabaseCfm *cfm)
{
    DEBUG_LOG("pairing_HandleCmDatabaseCfmRead opcode=%d result=0x%04x supplier=0x%04x",
              cfm->opcode,
              cfm->resultCode,
              cfm->resultSupplier);

    if (cfm->opcode == CSR_BT_CM_DB_OP_WRITE)
    {
        pairing_HandleCmDatabaseCfmWrite(cfm);
    }
}

void pairing_ProcessCrossTransKeyInd(pairing_plugin_context_t context)
{
    pairing_ctkd_rsp_t ctkd_response = {0};
    CmSmGenerateCrossTransKeyRequestInd ind;
    DEBUG_LOG_INFO("pairing_ProcessCrossTransKeyInd enum:pairingState:%d", pairing_GetState(PairingGetTaskData()));
    
    if(Pairing_PluginSmGetCtkdInd(context, &ind))
    {
        DEBUG_LOG_INFO("pairing_ProcessCrossTransKeyInd enum:CsrBtTransportType:%d RPA=%d addr=%04x %02x %06x flags:0x%x", 
            ind.tp_addrt.tp_type, ind.tp_addrt.addrt.type, ind.tp_addrt.addrt.addr.nap, ind.tp_addrt.addrt.addr.uap, ind.tp_addrt.addrt.addr.lap, ind.flags);
        
        pairing_plugin_rsp_t response = Pairing_PluginSmCtkdCallbacks(context, &ctkd_response);
        
        DEBUG_LOG_INFO("pairing_ProcessCrossTransKeyInd enum:pairing_plugin_rsp_t:%d, allow_ctkd=%d", response, ctkd_response.allow_ctkd);
        
        if(response != pairing_plugin_response_wait)
        {
            if(ctkd_response.allow_ctkd)
            {
                CmGenerateCrossTransKeyRequestResponse(ind.tp_addrt,
                                                       ind.identifier,
                                                       DM_SM_FLAG_GENERATE_CROSS_TRANS_KEY_ENABLE);
            }
            else
            {
                CmGenerateCrossTransKeyRequestResponse(ind.tp_addrt,
                                                       ind.identifier,
                                                       DM_SM_FLAG_GENERATE_CROSS_TRANS_KEY_DISABLE);
            }
        }
    }
}

static void pairing_HandlecmCrossTransKeyInd(const CmSmGenerateCrossTransKeyRequestInd *ind)
{
    
    DEBUG_LOG_FN_ENTRY("pairing_HandlecmCrossTransKeyInd %lx type:%x flags:%x identifier:%x", ind->tp_addrt.addrt.addr.lap,
              ind->type,
              ind->flags,
              ind->identifier);

    pairing_plugin_context_t context = Pairing_PluginSmSetCtkdInd(ind);
    pairing_ProcessCrossTransKeyInd(context);
}

static void pairing_HandleCmSmKeyRequestInd(DM_SM_KEY_REQUEST_IND_T *ind)
{
#ifndef INSTALL_CM_KEY_REQUEST_INDICATION
    UNUSED(ind);
    Panic();
#else
    DM_SM_UKEY_T ukey = { 0 };

    if (PairingGetTaskData()->key_indication_handler)
    {
        /* The container itself will be freed automatically, so we need to
         * reallocate it. Any contained pointers to data will need to be freed
         * by the client */
        DM_SM_KEY_REQUEST_IND_T *copy =
                PanicUnlessMalloc(sizeof(*copy));
        *copy = *ind;
        MessageSend(PairingGetTaskData()->key_indication_handler,
                    CM_PRIM,
                    copy);
        return;
    }

    CmKeyRequestResponse(ind->addrt, ind->security_requirements, ind->key_type, FALSE, ukey);
    CmFreeUpstreamMessageContents((void *) ind);
#endif
}

static void pairing_HandleCmPrim(Message message)
{
    CsrBtCmPrim *prim = (CsrBtCmPrim *) message;
    bool should_free = TRUE;

#ifdef ENABLE_LE_DEBUG_SECONDARY
        /*  Pairing module does not handle CM prims if its getting handled by callback */
        if (override_cm_handler_cb != NULL && override_cm_handler_cb(message))
        {
            DEBUG_LOG("pairing_HandleCmPrim:Prim handled by callback");
            return;
        }
#endif /* ENABLE_LE_DEBUG_SECONDARY */

    switch (*prim)
    {
        case CSR_BT_CM_SET_EVENT_MASK_CFM:
            /* Ignore */
            break;

        case CSR_BT_CM_ENCRYPT_CHANGE_IND:
            pairing_HandleCmEncryptChangeInd((const CsrBtCmEncryptChangeInd *) message);
            break;

        case CSR_BT_CM_DATABASE_CFM:
            pairing_HandleCmDatabaseCfm((const CsrBtCmDatabaseCfm *) message);
            break;

        case CSR_BT_CM_SM_AUTHORISE_IND:
            pairing_HandleCmSmAuthoriseInd((const CsrBtCmSmAuthoriseInd *) message);
            break;

        case CSR_BT_CM_SM_PIN_REQUEST_IND:
            pairing_HandleCmSmPinRequestInd((const CsrBtCmSmPinRequestInd *) message);
            break;

        case CSR_BT_CM_SM_IO_CAPABILITY_RESPONSE_IND:
            pairing_HandleCmSmIoCapabilityResponseInd((const CsrBtCmSmIoCapabilityResponseInd *) message);
            break;

        case CSR_BT_CM_SM_IO_CAPABILITY_REQUEST_IND:
            pairing_HandleCmSmIoCapabilityRequestInd((const CsrBtCmSmIoCapabilityRequestInd *) message);
            break;

        case CSR_BT_CM_SM_USER_CONFIRMATION_REQUEST_IND:
            pairing_HandleCmSmUserConfirmationRequestInd((const CsrBtCmSmUserConfirmationRequestInd *) message);
            break;

        case CSR_BT_CM_SM_USER_PASSKEY_REQUEST_IND:
            pairing_HandleCmSmUserPasskeyRequestInd((const CsrBtCmSmUserPasskeyRequestInd *) message);
            break;

        case CSR_BT_CM_SM_KEYPRESS_NOTIFICATION_IND:
            /* Ignore */
            break;

        case CSR_BT_CM_SM_SIMPLE_PAIRING_COMPLETE_IND:
            pairing_HandleCmSmSimplePairingCompleteInd((const CsrBtCmSmSimplePairingCompleteInd *) message);
            break;

        case CSR_BT_CM_SM_BONDING_CFM:
            pairing_HandleCmSmBondingCfm((const CsrBtCmSmBondingCfm *) message);
            break;

        case CSR_BT_CM_SM_SECURITY_CFM:
            pairing_HandleCmSmSecurityCfm((const CsrBtCmSmSecurityCfm *) message);
            break;

        case CSR_BT_CM_SM_SECURITY_IND:
            pairing_HandleCmSmSecurityInd((const CsrBtCmSmSecurityInd *) message);
            break;

        case CSR_BT_CM_WRITE_AUTH_PAYLOAD_TIMEOUT_CFM:
        case CM_DM_HCI_AUTHENTICATED_PAYLOAD_TIMEOUT_EXPIRED_IND:
            /* No special handling required, needs to be ignored. */
            break;

        case CSR_BT_CM_SECURITY_EVENT_IND:
            pairing_HandleCmSecurityEventInd((const CsrBtCmSecurityEventInd *) message);
            break;

        case CM_SM_KEY_REQUEST_IND:
            pairing_HandleCmSmKeyRequestInd((DM_SM_KEY_REQUEST_IND_T *) message);
            should_free = FALSE;
            break;

        case CM_SM_GENERATE_CROSS_TRANS_KEY_IND:
            pairing_HandlecmCrossTransKeyInd((const CmSmGenerateCrossTransKeyRequestInd *) message);
            break;

        default:
            break;
    }

    if (should_free)
    {
        CmFreeUpstreamMessageContents((void *) message);
    }
}

/*! \brief Message Handler

    This function is the main message handler for the pairing module.
*/
static void pairing_HandleMessage(Task task, MessageId id, Message message)
{
    pairingTaskData *thePairing = (pairingTaskData *) task;

    switch (id)
    {
        case PAIRING_INTERNAL_PAIR_REQ:
            pairing_HandleInternalPairRequest(thePairing, (PAIR_REQ_T *)message);
            break;

        case PAIRING_INTERNAL_LE_PEER_PAIR_REQ:
            pairing_HandleInternalPairLePeerRequest(thePairing, (PAIR_LE_PEER_REQ_T *)message);
            break;

        case PAIRING_INTERNAL_TIMEOUT_IND:
            pairing_HandleInternalTimeoutIndications(thePairing);
            break;

        case PAIRING_INTERNAL_DISCONNECT_IND:
            pairing_HandleInternalDisconnectIndication(thePairing);
            break;

        case PAIRING_INTERNAL_PAIR_STOP_REQ:
            pairing_HandleInternalPairStopReq(thePairing, (PAIRING_INTERNAL_PAIR_STOP_REQ_T *)message);
            break;

        case PAIRING_INTERNAL_LE_PAIR_TIMEOUT:
            /* After a configured timeout to disable le pairing expires, disallow le pairing */
            thePairing->state_machine.ble_permission = pairingBleDisallowed;
            thePairing->state_machine.smp_ctkd_ongoing = FALSE;
            DEBUG_LOG("Ble pairing disallowed. ble_permission : %d", thePairing->state_machine.ble_permission);
            break;
            
        case PAIRING_INTERNAL_CTKD_TIMEOUT:
            thePairing->state_machine.smp_ctkd_expected = FALSE;
            DEBUG_LOG("Timed out waiting for CTKD over BR/EDR-SM. Sending PAIRING_COMPLETE to clients.");
            Pairing_SendPairingCompleteMessageToClients();
            break;

        case CON_MANAGER_TP_DISCONNECT_IND:
            pairing_HandleTpDisconnectInd((const CON_MANAGER_TP_DISCONNECT_IND_T *)message);
            break;
        case CM_PRIM:
            pairing_HandleCmPrim(message);
            break;

        default:
            UnexpectedMessage_HandleMessage(id);
            break;
    }
}

bool Pairing_Init(Task init_task)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    UNUSED(init_task);

    DEBUG_LOG("Pairing_Init");

    /* Set up task handler */
    thePairing->task.handler = pairing_HandleMessage;

    TaskList_InitialiseWithCapacity(PairingGetClientList(), PAIRING_CLIENT_TASK_LIST_INIT_CAPACITY);

    /* Initialise state */
    thePairing->state_machine.state = PAIRING_STATE_NULL;
    thePairing->pairing_lock = 1;
    pairing_SetState(thePairing, PAIRING_STATE_INITIALISING);
    /* Do not enabled LE pairing always since it is a potential security issue. */
    thePairing->state_machine.ble_permission = pairingBleDisallowed;
    thePairing->pairing_clients[0].stop_task = NULL;
    pairing_ResetParams(&thePairing->pairing_clients[0].params);
    thePairing->state_machine.smp_ctkd_expected = FALSE;
    thePairing->state_machine.smp_ctkd_ongoing = FALSE;
    TaskList_InitialiseWithCapacity(PairingGetPairingActivity(), PAIRING_ACTIVITY_LIST_INIT_CAPACITY);
    thePairing->security_client_tasks = TaskList_Create();

    Pairing_PluginInit();

    /* register pairing as a client of the peer signalling task, it means
     * we will may get PEER_SIG_CONNECTION_IND messages if the signalling
     * channel becomes available again to retry sending the link key */
    appPeerSigClientRegister(&thePairing->task);

    Ui_RegisterUiProvider(ui_provider_handset_pairing, pairing_GetCurrentContext);

    ScActivateReqSend(&thePairing->task);
    CmSetEventMaskReqSend(&thePairing->task,
                          CSR_BT_CM_EVENT_MASK_SUBSCRIBE_ENCRYPT_CHANGE |
                          CSR_BT_CM_EVENT_MASK_SUBSCRIBE_SECURITY_EVENT_IND,
                          CSR_BT_CM_EVENT_MASK_COND_ALL);
    return TRUE;
}

void Pairing_PairWithParams(Task client_task, pairing_params_t* params)
{
    MAKE_PAIRING_MESSAGE(PAIR_REQ);
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("Pairing_PairWithParams");

    message->client_task = client_task;
    
    if(params)
    {
        message->params = *params;
    }
    else
    {
        pairing_ResetParams(&message->params);
    }

    MessageSendConditionally(&thePairing->task, PAIRING_INTERNAL_PAIR_REQ,
                             message, &thePairing->pairing_lock);
}

/*! \brief Pair with a device, where inquiry scanning is required. */
void Pairing_Pair(Task client_task, bool is_user_initiated)
{
    DEBUG_LOG("Pairing_Pair");
    pairing_params_t params;
    pairing_ResetParams(&params);
    params.is_user_initiated = is_user_initiated;
    
    if (is_user_initiated)
    {
        params.timeout_s = appConfigHandsetPairingTimeout();
    }
    
    Pairing_PairWithParams(client_task, &params);
}

/*! \brief Pair with a device where the address is already known.
 */
void Pairing_PairAddress(Task client_task, bdaddr* device_addr)
{
    DEBUG_LOG("Pairing_PairAddress");
    pairing_params_t params;
    pairing_ResetParams(&params);
    params.addr = *device_addr;
    Pairing_PairWithParams(client_task, &params);
}

/*! \brief Pair with le peer device. This is a temporary function until
           discovery is removed from the pairing module.
 */
void Pairing_PairLePeer(Task client_task, typed_bdaddr* device_addr, bool server)
{
    MAKE_PAIRING_MESSAGE(PAIR_LE_PEER_REQ);
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("Pairing_PairLePeer state %d", pairing_GetState(thePairing));

    message->client_task = client_task;
    message->le_peer_server = server;

    if(device_addr)
    {
        message->typed_addr = *device_addr;
    }
    else
    {
        BdaddrTypedSetEmpty(&message->typed_addr);
    }

    MessageSendConditionally(&thePairing->task, PAIRING_INTERNAL_LE_PEER_PAIR_REQ,
                             message, &thePairing->pairing_lock);
}

void Pairing_PairLeAddress(Task client_task, const typed_bdaddr* device_addr)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("Pairing_PairLeAddress state %d addr %04x %02x %06x",
              pairing_GetState(thePairing),
              device_addr->addr.nap,
              device_addr->addr.uap,
              device_addr->addr.lap);

    /*  Only supported in Discoverable state
     *  Pairing plugin handles handset pairing so don't interfere if one is set up */
    if (pairing_GetState(thePairing) == PAIRING_STATE_DISCOVERABLE)
    {
        CsrBtTypedAddr addrt;

        thePairing->pairing_clients[0].pair_le_task = client_task;
        pairing_ResetParams(&thePairing->pairing_clients[0].params);
        thePairing->state_machine.device_address.taddr = *device_addr;

        BdaddrConvertTypedVmToBluestack(&addrt, device_addr);

        CsrBtCmSmLeSecurityReqSend(addrt,
                                   L2CA_CONFLAG_ENUM(L2CA_CONNECTION_LE_SLAVE_DIRECTED),
                                   PAIRING_CONTEXT,
                                   DM_SM_SECURITY_AUTHENTICATED_BONDING);
    }
    else
    {
        pairing_SendNotReady(client_task);
    }
}

void Pairing_PairLeAddressAsMaster(Task client_task, const typed_bdaddr* device_addr)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("Pairing_PairWithConnectedLeDevice state %d addr %04x %02x %06x",
               pairing_GetState(thePairing),
               device_addr->addr.nap,
               device_addr->addr.uap,
               device_addr->addr.lap);

    if (pairing_GetState(thePairing) == PAIRING_STATE_IDLE)
    {
        CsrBtTypedAddr addrt;
        thePairing->pairing_clients[0].pair_le_task = client_task;
        pairing_ResetParams(&thePairing->pairing_clients[0].params);
        thePairing->state_machine.device_address.taddr = *device_addr;

        BdaddrConvertTypedVmToBluestack(&addrt, device_addr);
        CsrBtCmSmLeSecurityReqSend(addrt,
                                   L2CA_CONFLAG_ENUM(L2CA_CONNECTION_LE_MASTER_DIRECTED),
                                   PAIRING_CONTEXT,
                                   DM_SM_SECURITY_UNAUTHENTICATED_BONDING);

        pairing_SetState(thePairing, PAIRING_STATE_AWAITING_IO_CAPS_EXCHANGE);
    }
    else
    {
        pairing_SendNotReady(client_task);
    }
}

/*! \brief Register a task to receive security related notifications */
void Pairing_RegisterForSecurityEvents(Task task)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    PanicNull((void *)task);
    TaskList_AddTask(thePairing->security_client_tasks, task);
}

/*! \brief Unregister a task from receiving security related notifications */
void Pairing_UnregisterForSecurityEvents(Task task)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    PanicNull((void *)task);
    TaskList_RemoveTask(thePairing->security_client_tasks, task);
}

/*! @brief Stop a pairing.
 */
void Pairing_PairStop(Task client_task)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    MAKE_PAIRING_MESSAGE(PAIRING_INTERNAL_PAIR_STOP_REQ);

    DEBUG_LOG("Pairing_PairStop");

    message->client_task = client_task;

    MessageSend(&thePairing->task, PAIRING_INTERNAL_PAIR_STOP_REQ, message);
}

/* Register to receive #PAIRING_ACTIVITY messages. */
void Pairing_ActivityClientRegister(Task task)
{
    DEBUG_LOG_FN_ENTRY("Pairing_ActivityClientRegister");

    TaskList_AddTask(TaskList_GetFlexibleBaseTaskList(PairingGetPairingActivity()), task);
}

/* Unregister from #PAIRING_ACTIVITY messages. */
void Pairing_ActivityClientUnregister(Task task)
{
    DEBUG_LOG_FN_ENTRY("Pairing_ActivityClientUnregister");

    TaskList_RemoveTask(TaskList_GetFlexibleBaseTaskList(PairingGetPairingActivity()), task);
}

/*! brief Add a device to the PDL. */
void Pairing_AddAuthDevice(const bdaddr* address, const uint16 key_length, const uint16* link_key)
{
    pairingTaskData *thePairing = PairingGetTaskData();

    DEBUG_LOG("Pairing_AddAuthDevice");

    CsrBtTdDbBredrKey *bredrKey = PanicUnlessMalloc(sizeof(*bredrKey));
    CsrBtDeviceAddr deviceAddr;

    BdaddrConvertVmToBluestack(&deviceAddr, address);

    bredrKey->linkkeyType = DM_SM_LINK_KEY_UNAUTHENTICATED_P192;
    bredrKey->linkkeyLen = key_length;
    memcpy(&bredrKey->linkkey, link_key, key_length * sizeof(link_key[0]));
    bredrKey->authorised = FALSE;


    /* Store the link key in the PDL */
    CmWriteBredrKeysReqSend(&thePairing->task,
                            CSR_BT_ADDR_PUBLIC,
                            &deviceAddr,
                            (CsrBtCmKey *) bredrKey);
}


device_t Pairing_AddBondedDevice(const bdaddr* address, deviceType device_type, uint16 flags,
                                 const PAIRING_LONG_TERM_KEY_T *bredr_lk,
                                 const PAIRING_LONG_TERM_KEY_T *le_ltk,
                                 const PAIRING_LONG_TERM_KEY_T* le_irk)
{
    device_t device = BtDevice_GetDeviceForBdAddr(address);

    DEBUG_LOG_INFO("Pairing_AddBondedDevice, enum:deviceType:%u, lap=0x%06lx", device_type, address->lap);

    /* If the device already exists, don't go any further */
    if (device)
    {
        DEBUG_LOG_ERROR("Pairing_AddBondedDevice, device (%p) already exists", device);
        return NULL;
    }
    else if (!bredr_lk && !le_ltk)
    {
        DEBUG_LOG_ERROR("Pairing_AddBondedDevice, no link keys provided");
        return NULL;
    }

    if (bredr_lk)
    {
        DEBUG_LOG_VERBOSE("Pairing_AddBondedDevice, BR/EDR Link Key: ");
        DEBUG_LOG_DATA_VERBOSE((void *)bredr_lk->key, sizeof(bredr_lk->key));

        CsrBtTdDbBredrKey bredrKey = { 0 };
        CsrBtDeviceAddr deviceAddr = { 0 };

        BdaddrConvertVmToBluestack(&deviceAddr, address);

        bredrKey.linkkeyType = DM_SM_LINK_KEY_AUTHENTICATED_P256;
        bredrKey.linkkeyLen = sizeof(bredr_lk->key);
        memcpy(&bredrKey.linkkey, bredr_lk->key, sizeof(bredrKey.linkkey));
        bredrKey.authorised = TRUE;

        /* Add the bredr keys to PS */
        if (CmWriteKeysNow(&deviceAddr,
                            CSR_BT_ADDR_PUBLIC,
                            CSR_BT_TD_DB_SC_KEY_BREDR_KEY,
                            &bredrKey) != CSR_BT_RESULT_CODE_TD_DB_SUCCESS)
        {
            DEBUG_LOG_ERROR("Pairing_AddBondedDevice, failed adding BR/EDR keys");
            return NULL;
        }
    }

    if (le_ltk)
    {
        DEBUG_LOG_VERBOSE("Pairing_AddBondedDevice, LE LTK: ");
        DEBUG_LOG_DATA_VERBOSE((void *)le_ltk->key, sizeof(le_ltk->key));

        CsrBtTdDbLeKeys leKeys = { 0 };
        CsrBtDeviceAddr deviceAddr = { 0 };

        BdaddrConvertVmToBluestack(&deviceAddr, address);

        /* Populate the LE LTK */
        memcpy(&leKeys.encCentral.ltk, &le_ltk->key, sizeof(leKeys.encCentral.ltk));
        leKeys.secReq = DM_SM_SECURITY_SECURE_CONNECTIONS | DM_SM_SECURITY_ENCRYPTION |
                        DM_SM_SECURITY_BONDING | DM_SM_SECURITY_MITM_PROTECTION;
        leKeys.keySize = sizeof(&le_ltk->key);
        leKeys.keyValid = CSR_BT_TD_DB_LE_KEY_ENC_CENTRAL;

        /* Populate the LE IRK, if it exists */
        if (le_irk)
        {
            DEBUG_LOG_VERBOSE("Pairing_AddBondedDevice, LE IRK: ");
            DEBUG_LOG_DATA_VERBOSE((void *)le_irk->key, sizeof(le_irk->key));

            memcpy(&leKeys.id.irk, &le_irk->key, sizeof(leKeys.id.irk));
            leKeys.keyValid |= CSR_BT_TD_DB_LE_KEY_ID;
        }

        /* Add the LE keys to PS */
        if (CmWriteKeysNow(&deviceAddr,
                            CSR_BT_ADDR_PUBLIC,
                            CSR_BT_TD_DB_SC_KEY_LE_KEYS,
                            &leKeys) != CSR_BT_RESULT_CODE_TD_DB_SUCCESS)
        {
            DEBUG_LOG_ERROR("Pairing_AddBondedDevice, failed adding LE keys");
            return NULL;
        }
    }

    /* Create an entry for the device attributes. Initialize properties. */
    device = BtDevice_GetDeviceCreateIfNew(address, device_type);

    if (!Device_SetPropertyU16(device, device_property_flags, flags) ||
        !BtDevice_SetDefaultProperties(device))
    {
        DEBUG_LOG_ERROR("Pairing_AddBondedDevice, failed to set device properties");
        return NULL;
    }
    else
    {
        DeviceDbSerialiser_SerialiseDevice(device);
    }

    DEBUG_LOG_INFO("Pairing_AddBondedDevice, device(%p), lap=0x%06lx, success", device, address->lap);
    return device;
}

static void pairing_RegisterMessageGroup(Task task, message_group_t group)
{
    PanicFalse(group == PAIRING_MESSAGE_GROUP);
    TaskList_AddTask(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), task);
}

void Pairing_RegisterNotificationClient(Task task)
{
    TaskList_AddTask(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), task);
}

void Pairing_RegisterKeyIndicationHandler(Task task)
{
    PanicFalse(PairingGetTaskData()->key_indication_handler == NULL);
    PairingGetTaskData()->key_indication_handler = task;
}

/*
 * TEST FUNCTIONS
 */
void Pairing_SetLinkTxReqd(void)
{
    bdaddr handset_addr;

    DEBUG_LOG("Setting handset link key TX is required");
    appDeviceGetHandsetBdAddr(&handset_addr);
    BtDevice_SetHandsetLinkKeyTxReqd(&handset_addr, TRUE);
}

void Pairing_ClearLinkTxReqd(void)
{
    bdaddr handset_addr;

    DEBUG_LOG("Pairing_ClearLinkTxReqd");
    appDeviceGetHandsetBdAddr(&handset_addr);
    BtDevice_SetHandsetLinkKeyTxReqd(&handset_addr, FALSE);
}

void Pairing_SendPairingCompleteMessageToClients(void)
{
    DEBUG_LOG("Pairing_SendPairingCompleteMessageToClients");
    TaskList_MessageSendId(TaskList_GetFlexibleBaseTaskList(PairingGetClientList()), PAIRING_COMPLETE);
}

#ifdef ENABLE_LE_DEBUG_SECONDARY
void Pairing_OverrideCmPrimHandler(const pairing_override_cm_handler_callback_t callback)
{
   override_cm_handler_cb = callback;
}
#endif

MESSAGE_BROKER_GROUP_REGISTRATION_MAKE(PAIRING, pairing_RegisterMessageGroup, NULL);

